/*
 * Copyright (c) 2006 Israfil Consulting Services Corporation
 * Copyright (c) 2006 Christian Edward Gruber
 * All Rights Reserved
 * 
 * This software is licensed under the Berkeley Standard Distribution license,
 * (BSD license), as defined below:
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this 
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of Israfil Consulting Services nor the names of its contributors 
 *    may be used to endorse or promote products derived from this software without 
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
 * OF SUCH DAMAGE.
 * 
 * $Id: CompCompilerMojo.java 485 2007-05-03 17:30:39Z cgruber $
 */
package net.israfil.mojo.flex2;

import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

import javax.xml.parsers.SAXParser;

import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;


/**
 * Compilation action for Flex2 actionscript 3 classes.
 * 
 * @author <a href="cgruber@israfil.net">Christian Edward Gruber</a>
 * @version $Id: CompCompilerMojo.java 485 2007-05-03 17:30:39Z cgruber $
 * 
 * @goal compile-swc
 * @requiresDependencyResolution
 * @phase compile
 * @requiresProject
 */
public class CompCompilerMojo extends AbstractFlexMojo {

    /**
     * 
     * 
     * @parameter expression="${flex.compiler.debug}" default="true"
     */
    protected boolean debug;
	
    /**
     * An optional file provided which provides a list of classes and resources to be 
     * included in the swc.  The format of this file is identical to that generated
     * by the flex builder file .flexLibProperties, and the .flexLibProperties can be
     * used and set here.
     * 
	 * @parameter alias="properties" 
     */
    protected File flexLibPropertiesFile;
    
    /**
     * An optional list of included classes.  If this is present, then only those
     * classes present in this list will be included in the .swc.
     * 
	 * @parameter alias="classes" 
     */
    protected List includedClasses;
    
    /**
     * An optional list of included resources.  If present, then the specified 
     * resources will be included from src/main/resources in the swc.  If resources
     * are not specified here and no flex lib properties file is specified, then the
     * full contents of src/main/resources will be included in the .swc, except for 
     * SCM related files (.svn, or CVS/)
     * 
     * @parameter alias="resources" 
     */
    protected List includedResources;
	
	public CompCompilerMojo() {
		super();
	}
	
	protected String getCompilerClass() { return "flex2.tools.Compc"; }
	
    protected  String getFileExtension() { return "swc"; }

    public void finalizeParameters(List parameterList) throws MojoFailureException, MojoExecutionException {
    	
		parameterList.add("-compiler.debug=" + debug);    	
		
		addSourcePath(parameterList);
		
		if (flexLibPropertiesFile == null) {
			getLog().debug("No flex lib properties specified - checking for included resources or classes.");
			if (includedClasses == null) {
				includeSourceRoots(parameterList);
			} 
			if (includedResources == null) {
		    	includeAllResources();			
			} 
		} else {
			getLog().debug("Flex lib properties file specified.");
			processFlexLibPropertiesFile();
		}
		
		if (includedClasses != null && !includedClasses.isEmpty()) {
	    	parameterList.add( "-include-classes" );
	    	for (Iterator i = includedClasses.iterator() ; i.hasNext();) 
	    		parameterList.add( i.next() );
		}
		
		if (includedResources != null && !includedResources.isEmpty()) {
			for (Iterator i = includedResources.iterator() ; i.hasNext();) {
				List resourceRoots = new ArrayList(compileSourceRoots);
				resourceRoots.add("src/main/resources/");
				ComponentResource cr = (ComponentResource)i.next();
		    	parameterList.add( "-include-file" );
	    		parameterList.add( cr.getDestPath() );
	    		File file =  new File(cr.getSourcePath()).getAbsoluteFile();
	    		for (int c = 0; c < resourceRoots.size() && !file.exists() ; c++) {
	    			file = new File((String)resourceRoots.get(c), cr.getSourcePath()).getAbsoluteFile();
	    			getLog().debug("Attempting to use file: " + file.getAbsolutePath());
	    		}
	    		if (file == null || !file.exists()) 
	    			throw new MojoFailureException("Cannot load file for path " + cr.getSourcePath());
	    		parameterList.add(file.getAbsolutePath());
    			getLog().debug("Adding resource: " + file.getAbsolutePath());
			}
		}
    }

	private void includeSourceRoots(List parameterList) {
		for (Iterator i = compileSourceRoots.iterator();i.hasNext();) {
			String path = (String)i.next();
			if (new File(path).exists()) {
				getLog().debug("Adding source path: " + path);
		    	parameterList.add( "-include-sources" );
		    	parameterList.add( path );
			} else {
				getLog().debug("Ignoring missing source path: " + path);
			}
		}
	}
	private void includeAllResources() {
		List resourcesRootPaths = new ArrayList(compileSourceRoots);
		resourcesRootPaths.add("src/main/resources/");
		for (int i = 0; i < resourcesRootPaths.size(); i++) {
			File resourcesRoot = new File((String)resourcesRootPaths.get(i));
			if (!resourcesRoot.exists()) continue;
			for (Iterator iter = find(resourcesRoot,".*[.](?!as|mxml).*$",true).iterator();iter.hasNext();) {
				String path = (String)iter.next();
				ComponentResource cr = new ComponentResource();
				File file = new File(path);
				cr.setSourcePath(file.getAbsolutePath());
				String dest = path.substring(((String)resourcesRootPaths.get(i)).length());
				cr.setDestPath(dest);
				if (includedResources == null) includedResources = new ArrayList();
				getLog().debug("Adding resource: " + cr);
				includedResources.add(cr);
			}
		}
	}
	
	public List find(File directory, String pattern, boolean ignoreVersionControlFiles) {
		Pattern p = Pattern.compile(pattern);
		List files = new ArrayList();
		Set directories = new HashSet();
		File children[] = directory.listFiles();
		for (int i = 0; i < children.length; i++) {
		    if (children[i].isDirectory() && !directories.contains(children[i])) {
		    	directories.add(children[i]); // keep link loops at bay.
				if (!ignoreVersionControlFiles || 
						(
							!children[i].getName().equalsIgnoreCase(".svn") &&
							!children[i].getName().equalsIgnoreCase("CVS") 
						)
					)
		    		files.addAll(find(children[i], pattern, ignoreVersionControlFiles));
		    } else if (p.matcher(children[i].getPath()).find()) 
				if (!ignoreVersionControlFiles || !children[i].getName().startsWith(".")) 
					files.add(children[i].getPath());
		}
		return files;
	}


	private void processFlexLibPropertiesFile() throws MojoExecutionException {
		if (!flexLibPropertiesFile.exists()) 
			throw new MojoExecutionException("Flex properties file \"" + flexLibPropertiesFile.getAbsolutePath() + "\" does not exist.");

		FlexLibPropertiesHandler handler = new FlexLibPropertiesHandler();
		try {
			getLog().debug("Parsing " + flexLibPropertiesFile.getAbsolutePath());
			SAXParser parser = javax.xml.parsers.SAXParserFactory.newInstance().newSAXParser();
			parser.parse(flexLibPropertiesFile, handler);
			getLog().debug("Specified classes: " + handler.getClasses());
			for (Iterator i = handler.getClasses().iterator(); i.hasNext() ;) {
				if (includedClasses == null) includedClasses = new ArrayList();
				includedClasses.add(0,i.next()); // include first
			}
			getLog().debug("Specified resources: " + handler.getResources());
			for (Iterator i = handler.getResources().iterator(); i.hasNext() ;) {
				if (includedResources == null) includedResources = new ArrayList();
				includedResources.add(0,i.next()); // include first
			}	
		} catch (Exception e) {
			throw new MojoExecutionException("Exception parsing flex lib properties file: " + flexLibPropertiesFile.getName(),e );
		}
	}

    
    private static class FlexLibPropertiesHandler extends DefaultHandler {
    	
    	List classes;
    	List resources;
    	final Log log; 
    	
    	public FlexLibPropertiesHandler() {
    		this.log = new org.apache.maven.plugin.logging.SystemStreamLog();
    	}
    	
    	public List getClasses() { return classes; }
    	
    	public List getResources() { return resources; } 

		public void startDocument() throws SAXException {
			classes = new ArrayList();
			resources = new ArrayList();
			super.startDocument();
		}

		public void startElement(String uri, String localname, String qName, Attributes attributes) throws SAXException {
			super.startElement(uri, localname, qName, attributes);
			String tag = (localname != null && !localname.equals("")) ? 
						 localname : qName;
			if ("classEntry".equals(tag)) {
				if (log.isDebugEnabled())
					log.debug("Adding class: " + attributes.getValue(uri, "path"));
				classes.add(attributes.getValue(uri, "path"));
			} else if ("resourceEntry".equals(tag)) {
				ComponentResource cr = new ComponentResource();
				cr.setSourcePath(attributes.getValue(uri, "sourcePath"));
				cr.setDestPath(attributes.getValue(uri, "destPath"));
				if (log.isDebugEnabled())
					log.debug("Adding resource: " + cr);
				resources.add(cr);
			}
		}

		public void endDocument() throws SAXException {
			super.endDocument();
		}

    }

}
